home *** CD-ROM | disk | FTP | other *** search
- /* Copyright (c) 1987, 1988 Stanley T. Shebs, University of Utah. */
- /* This program may be used, copied, modified, and redistributed freely */
- /* for noncommercial purposes, so long as this notice remains intact. */
-
- #pragma comment(exestr, "@(#) draw.c 12.1 95/05/09 ")
-
- /* RCS $Header: draw.c,v 1.3 88/07/18 17:10:44 shebs Exp $ */
-
- /* Geometry is somewhat tricky because our viewports are supposed */
- /* to wrap around a cylinder transparently. The general idea is that */
- /* if modulo opns map x coordinates onto a cylinder, adding and subtracting */
- /* the diameter of the cylinder "un-modulos" things. If this doesn't make */
- /* any sense to you, then be careful about fiddling with the code! */
-
- /* If that wasn't bad enough, the hexes constitute an oblique coordinate */
- /* where the axes form a 60 degree angle to each other. Fortunately, no */
- /* trig is necessary - to convert to/from rectangular, add/subtract 1/2 of */
- /* the y coordinate to x, and leave the y coordinate alone. */
-
- /* The graphical code uses mostly text drawing functions, which are more */
- /* likely to be efficient than is random bitblting. (The interface may */
- /* implement the operations as random blitting, but that's OK.) */
-
- #include "config.h"
- #include "misc.h"
- #include "period.h"
- #include "side.h"
- #include "unit.h"
- #include "map.h"
-
- extern bool populations; /* used to decide about running pop display */
-
- char rowbuf[BUFSIZE]; /* buffer for terrain row drawing */
-
- /* redraw() has moved to the display code in X11.c and curses.c */
-
- /* Ensure that given location is visible. We also flush the input because */
- /* any input relating to a different screen is probably worthless. */
-
- put_on_screen(side, x, y)
- Side *side;
- int x, y;
- {
- /* Ugly hack to prevent extra boxes being drawn during init - don't ask!*/
- if (x == 0 && y == 0) return;
- if (active_display(side)) {
- if (!in_middle(side, x, y)) {
- side->vcx = wrap(x);
- side->vcy = min(max(side->vh2-(1-(side->vh&1)), y),
- (world.height-1)-side->vh2);
- if (side->lastvcx >= 0) undraw_box(side);
- show_map(side);
- if (side->lastvcx >= 0) draw_box(side);
- flush_output(side);
- flush_input(side);
- }
- }
- }
-
- /* Decide whether given location is not too close to edge of screen. */
- /* We do this because it's a pain to move units when half the adjacent */
- /* places aren't even visible. This routine effectively places a lower */
- /* limit of 5x5 for the map window. (I think) */
-
- in_middle(side, x, y)
- Side *side;
- int x, y;
- {
- int vcx = side->vcx, vcy = side->vcy;
- int vw2 = side->vw2, vh2 = side->vh2;
- int top, bot;
-
- /* handle the boundary condition near top and bottom */
- bot = vcy - vh2 + 2;
- if (bot == 2) bot = 1;
- top = vcy + vh2 - 2;
- if (top == world.height-3) top = world.height - 2;
- if (!between(bot, y, top))
- return FALSE;
- x = unwrap(side, x + (y - vcy) / 2);
- return between(vcx-vw2+2, x, vcx+vw2-3+(side->vw&1));
- }
-
- /* Transform map coordinates into screen coordinates, relative to the given */
- /* side. Allow for cylindricalness and number of pixels in a hex. */
-
- xform(side, x, y, sxp, syp)
- Side *side;
- int x, y, *sxp, *syp;
- {
- *sxp = ((side->hw * (x - (side->vcx - side->vw2))) +
- (side->hw * (y - side->vcy)) / 2);
- *syp = side->hch * ((side->vcy + side->vh2) - y);
- }
-
- /* Undo the wrapping effect, relative to viewport location. */
- /* Note that both conditions cannot both be true at the same time, */
- /* since viewport is smaller than map. */
-
- unwrap(side, x)
- Side *side;
- int x;
- {
- int vcx = side->vcx, vw2 = side->vw2, vw34 = side->vw2 + (side->vw2 >> 1);
-
- if (vcx - vw2 < 0 && x > vcx + vw34) x -= world.width;
- if (vcx + vw2 > world.width-1 && x < vcx - vw34) x += world.width;
- return x;
- }
-
- /* Un-transform screen coordinates (as supplied by mouse perhaps) into */
- /* map coordinates. This doesn't actually account for the details of */
- /* hexagonal boundaries, and actually discriminates box-shaped areas. */
-
- deform(side, sx, sy, xp, yp)
- Side *side;
- int sx, sy, *xp, *yp;
- {
- int vcx = side->vcx, vcy = side->vcy, adjust;
-
- *yp = (vcy + side->vh2) - (sy / side->hch);
- adjust = (((*yp - vcy) & 1) ? ((side->hw/2) * (*yp >= vcy ? 1 : -1)) : 0);
- *xp = wrap(((sx - adjust) / side->hw) - (*yp - vcy) / 2 +
- (vcx - side->vw2));
- }
-
- /* Transform coordinates in the world display. Simpler, since no moving */
- /* viewport nonsense. */
-
- w_xform(side, x, y, sxp, syp)
- Side *side;
- int x, y, *sxp, *syp;
- {
- *sxp = side->mm * x + (side->mm * y) / 2;
- *syp = side->mm * (world.height - 1 - y);
- }
-
- /* Redraw the map of the whole world. We use square blobs instead of icons, */
- /* since individual "hexes" may be as little as 1x1 pixels in size, and */
- /* there are lots of them. Algorithm uses run-length encoding to find and */
- /* draw bars of constant color. Monochrome just draws units - no good */
- /* choices for terrain display. */
-
- show_world(side)
- Side *side;
- {
- int x, y, color, barcolor, x1;
-
- if (active_display(side) && world_display(side)) {
- clear_window(side, side->world);
- for (y = world.height-1; y >= 0; --y) {
- x1 = 0;
- barcolor = world_color(side, x1, y);
- for (x = 0; x < world.width; ++x) {
- color = world_color(side, x, y);
- if (color != barcolor) {
- draw_bar(side, x1, y, x - x1, barcolor);
- x1 = x;
- barcolor = color;
- }
- }
- draw_bar(side, x1, y, world.width - x1, barcolor);
- }
- if (side->vcy >= 0) draw_box(side);
- }
- }
-
- /* Compute the color representing a hex from the given side's point of view. */
-
- world_color(side, x, y)
- Side *side;
- int x, y;
- {
- int view = side_view(side, x, y);
- Side *side2;
-
- if (side->monochrome) {
- return ((view == UNSEEN || view == EMPTY) ? side->bgcolor :
- side->fgcolor);
- } else {
- if (view == UNSEEN) {
- return (side->bgcolor);
- } else if (view == EMPTY) {
- return (side->hexcolor[terrain_at(x, y)]);
- } else {
- side2 = side_n(vside(view));
- return ((side2 == NULL) ? side->neutcolor :
- (allied_side(side2, side) ? side->altcolor :
- side->enemycolor));
- }
- }
- }
-
- /* Draw an outline box on the world map. Since we adopt the dubious trick */
- /* of inverting through all planes, must be careful to undo before moving; */
- /* also, draw/undraw shiftedly so both boxes appear on both sides of world. */
-
- draw_box(side)
- Side *side;
- {
- invert_box(side, side->vcx, side->vcy);
- invert_box(side, side->vcx - world.width, side->vcy);
- side->lastvcx = side->vcx; side->lastvcy = side->vcy;
- }
-
- undraw_box(side)
- Side *side;
- {
- invert_box(side, side->lastvcx, side->lastvcy);
- invert_box(side, side->lastvcx - world.width, side->lastvcy);
- }
-
- /* Draw immediate area in more detail. We make a little effort to avoid */
- /* drawing hexes off the visible part of the screen, but are still somewhat */
- /* conservative, so as not to get holes in the display. Implication is that */
- /* some lower level of routines has to be able to clip the map window. */
-
- show_map(side)
- Side *side;
- {
- int y1, y2, y, x1, x2, adj;
-
- if (active_display(side)) {
- clear_window(side, side->map);
- y1 = side->vcy + side->vh2;
- y2 = side->vcy - side->vh2 + 1 - (side->vh & 1);
- for (y = y1; y >= y2; --y) {
- adj = (y - side->vcy) / 2;
- x1 = side->vcx - side->vw2 - adj - 1;
- x2 = side->vcx + side->vw2 - adj + 1 + (side->vw & 1);
- draw_row(side, x1, y, x2 - x1);
- }
- draw_cursor(side);
- flush_output(side);
- /* draw_box(side); /* must be after flush */
- }
- }
-
- /* Draw an individual detailed hex, as a row of one. */
- /* This routine may be called in cases where the hex is not on the main */
- /* screen; if so, then the world map but not the local map is drawn on. */
- /* (should the display be shifted to make visible?) */
-
- draw_hex(side, x, y, flushit)
- Side *side;
- int x, y;
- bool flushit;
- {
- int sx, sy;
-
- if (active_display(side)) {
- if (side->monochrome || side->showmode == TERRICONS) {
- xform(side, unwrap(side, x), y, &sx, &sy);
- draw_hex_icon(side, side->map, sx, sy, side->bgcolor, HEX);
- }
- draw_row(side, unwrap(side, x), y, 1);
- draw_bar(side, x, y, 1, world_color(side, x, y));
- if (flushit) flush_output(side);
- }
- }
-
- /* The basic map drawing routine does an entire row at a time, which yields */
- /* order-of-magnitude speedups (!). This routine is complicated by several */
- /* tricks: 1) in monochrome, the entire line can be drawn at once; 2) in */
- /* color, run-length encoding maximizes the length of constant-color strings */
- /* and 3) anything which is in the background color need not be drawn. */
- /* In general, this routine dominates the map viewing process, so efficiency */
- /* here is very important. */
-
- draw_row(side, x0, y0, len)
- Side *side;
- int x0, y0, len;
- {
- bool empty = TRUE;
- char ch;
- int i = 0, x, x1, color, segcolor, sx, sy;
-
- if (side->monochrome) {
- xform(side, x0, y0, &sx, &sy);
- for (x = x0; x < x0 + len; ++x) {
- if (side_view(side, wrap(x), y0) == EMPTY) {
- rowbuf[i++] = ttypes[terrain_at(wrap(x), y0)].tchar;
- empty = FALSE;
- } else {
- rowbuf[i++] = ' ';
- }
- }
- if (!empty) draw_terrain_row(side, sx, sy, rowbuf, i, side->fgcolor);
- } else {
- x1 = x0;
- segcolor = hex_color(side, x0, y0);
- for (x = x0; x < x0 + len; ++x) {
- color = hex_color(side, x, y0);
- if (color != segcolor) {
- if (segcolor != side->bgcolor) {
- xform(side, x1, y0, &sx, &sy);
- draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
- }
- i = 0;
- x1 = x;
- segcolor = color;
- }
- switch(side->showmode) {
- case FULLHEX:
- case BOTHICONS:
- ch = HEX;
- break;
- case BORDERHEX:
- ch = OHEX;
- break;
- case TERRICONS:
- ch = ttypes[terrain_at(wrap(x), y0)].tchar;
- break;
- }
- rowbuf[i++] = ch;
- }
- if (len == 1) i = 1;
- xform(side, x1, y0, &sx, &sy);
- draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
- if (side->showmode == BOTHICONS) {
- i = 0;
- x1 = x0;
- segcolor = terricon_color(side, x0, y0);
- for (x = x0; x < x0 + len; ++x) {
- color = terricon_color(side, x, y0);
- if (color != segcolor) {
- xform(side, x1, y0, &sx, &sy);
- draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
- i = 0;
- x1 = x;
- segcolor = color;
- }
- rowbuf[i++] = ttypes[terrain_at(wrap(x), y0)].tchar;
- }
- if (len == 1) i = 1;
- xform(side, x1, y0, &sx, &sy);
- draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
- }
- }
- /* Units are much harder to optimize - fortunately they're sparse */
- for (x = x0; x < x0 + len; ++x) {
- draw_unit(side, x, y0);
- }
- }
-
- /* Return the color of the hex. (for color displays only) */
-
- hex_color(side, x, y)
- Side *side;
- int x, y;
- {
- return ((side_view(side, wrap(x), y) == UNSEEN) ? side->bgcolor :
- side->hexcolor[terrain_at(wrap(x), y)]);
- }
-
- /* Return the color of a terrain icon overlaying a colored hex. */
-
- terricon_color(side, x, y)
- Side *side;
- int x, y;
- {
- return ((side_view(side, wrap(x), y) == UNSEEN) ? side->bgcolor :
- (ttypes[terrain_at(wrap(x), y)].dark ? side->fgcolor :
- side->bgcolor));
- }
-
- /* Draw a single unit icon as appropriate. This *also* has a bunch of */
- /* details to worry about: centering of icon in hex, clearing a rectangular */
- /* area for the icon, picking a color for the unit, using either a bitmap */
- /* or font char, and adding a side number for many-player games. */
- /* Must also be careful not to draw black-on-black for units in space. */
- /* This routine has also been drafted into drawing populace side numbers */
- /* for otherwise empty hexes. */
-
- draw_unit(side, x, y)
- Side *side;
- int x, y;
- {
- int view = side_view(side, wrap(x), y), sx, sy, ucolor, hcolor, n;
- int terr = terrain_at(wrap(x), y), pop, pcolor;
- Side *side2;
-
- if (view != UNSEEN) {
- if (view == EMPTY) {
- if (populations) {
- pop = people_at(wrap(x), y);
- if (pop != NOBODY) {
- side2 = side_n(pop-8);
- pcolor = (allied_side(side, side2) ? side->owncolor :
- (enemy_side(side, side2) ? side->enemycolor :
- side->neutcolor));
- if (pcolor == side->owncolor &&
- (ttypes[terr].dark ||
- side->monochrome ||
- (side->showmode == TERRICONS)))
- pcolor = side->fgcolor;
- xform(side, x, y, &sx, &sy);
- draw_side_number(side, side->map, sx, sy, pop-8, pcolor);
- }
- }
- } else {
- xform(side, x, y, &sx, &sy);
- side2 = side_n(vside(view));
- ucolor = (allied_side(side, side2) ? side->owncolor :
- (enemy_side(side, side2) ? side->enemycolor :
- side->neutcolor));
- if (ucolor == side->owncolor &&
- (ttypes[terr].dark ||
- side->monochrome ||
- (side->showmode == TERRICONS)))
- ucolor = side->fgcolor;
- if (side->monochrome && side != side2)
- ucolor = side->bgcolor;
- hcolor = (side == side2 ? side->bgcolor : side->fgcolor);
- if (side->monochrome) {
- /* erasing background */
- draw_hex_icon(side, side->map, sx, sy, hcolor, HEX);
- } else if (side->showmode != TERRICONS) {
- draw_hex_icon(side, side->map, sx, sy, hex_color(side, x, y),
- ((side->showmode == BORDERHEX) ? OHEX : HEX));
- }
- draw_unit_icon(side, side->map, sx, sy, vtype(view), ucolor);
- n = side_number(side2);
- if ((numsides > 2 || side->monochrome) && n != side_number(side)) {
- draw_side_number(side, side->map, sx, sy, n, ucolor);
- }
- }
- }
- }
-
- /* Cursor drawing also draws the unit in some other color if it's not the */
- /* "top-level" unit in a hex, as well as getting the player's attention */
- /* if the new location is sufficiently far from the last. */
-
- draw_cursor(side)
- Side *side;
- {
- int sx, sy;
-
- if (active_display(side)) {
- /* ugly hack to prevent extra cursor draw */
- if (side->cury == 0) return;
- xform(side, unwrap(side, side->curx), side->cury, &sx, &sy);
- if (side->curunit != NULL && side->curunit->transport != NULL) {
- if (side->monochrome) {
- draw_hex_icon(side, side->map, sx, sy, side->bgcolor, HEX);
- draw_unit_icon(side, side->map, sx, sy,
- side->curunit->type, side->fgcolor);
- } else {
- draw_unit_icon(side, side->map, sx, sy,
- side->curunit->type, side->diffcolor);
- }
- }
- /* Flash something to draw the eye a long ways */
- if (humanside(side)) {
- if (distance(side->curx, side->cury, side->lastx, side->lasty) > 3)
- flash_position(side, sx, sy, DELAY);
- draw_cursor_icon(side, sx, sy);
- side->lastx = side->curx; side->lasty = side->cury;
- }
- }
- }
-
- /* Get rid of cursor by redrawing the hex. */
-
- erase_cursor(side)
- Side *side;
- {
- if (side->lasty > 0) draw_hex(side, side->lastx, side->lasty, TRUE);
- }
-
- int fix_mushroom_hex();
-
- /* Draw a splat visible to both sides at a given location. Several splats */
- /* available, depending on the seriousness of the hit. Make an extra-flashy */
- /* display when The Bomb goes off. Because of the time delays involved, we */
- /* have to update both sides' displays more or less simultaneously. Would be */
- /* better to exhibit to all sides maybe, but I'm not going to bother! */
-
- draw_blast(unit, es, hit)
- Unit *unit;
- Side *es;
- int hit;
- {
- char ch;
- int ux = unit->x, uy = unit->y, sx, sy, i;
- Side *us = unit->side;
-
- if (hit >= period.nukehit) {
- if (active_display(us)) invert_whole_map(us);
- if (active_display(es)) invert_whole_map(es);
- /* may need a time delay if X is too speedy */
- nap(500);
- if (active_display(us)) invert_whole_map(us);
- if (active_display(es)) invert_whole_map(es);
- for (i = 0; i < 4; ++i) {
- if (active_display(us)) draw_mushroom(us, ux, uy, i);
- if (active_display(es)) draw_mushroom(es, ux, uy, i);
- if (i != 2 && (active_display(us) || active_display(es))) sleep(1);
- }
- if (active_display(us) || active_display(es)) sleep(1);
- tmpside = us;
- apply_to_area(ux,uy,2,fix_mushroom_hex);
- flush_output(us);
- tmpside = es;
- apply_to_area(ux,uy,2,fix_mushroom_hex);
- flush_output(es);
- } else {
- ch = ((hit >= unit->hp) ? 'd' : ((hit > 0) ? 'c' : 'b'));
- if (active_display(us)) {
- xform(us, unwrap(us, ux), uy, &sx, &sy);
- draw_blast_icon(us, us->map, sx, sy, ch, us->enemycolor);
- flush_output(us);
- nap(250);
- draw_hex(us,ux,uy,TRUE);
- }
- if (active_display(es)) {
- xform(es, unwrap(es, ux), uy, &sx, &sy);
- draw_blast_icon(es, es->map, sx, sy, ch, es->owncolor);
- flush_output(es);
- nap(250);
- draw_hex(es, ux, uy,TRUE);
- }
- }
- }
-
- /* Draw all the units in a column. They should be spaced so they don't */
- /* overlap or get misaligned with text, and can be inverted if desired. */
-
- draw_unit_list(side, hilite)
- Side *side;
- bool hilite[];
- {
- int u, pos = 0, spacing = max(side->hh, side->fh);
-
- if (active_display(side)) {
- for_all_unit_types(u) {
- draw_hex_icon(side, side->state, side->margin, pos,
- (hilite[u] ? side->fgcolor : side->bgcolor), OHEX);
- draw_unit_icon(side, side->state, side->margin, pos, u,
- (hilite[u] ? side->bgcolor : side->fgcolor));
- pos += spacing;
- }
- }
- }
-
- fix_mushroom_hex(x,y)
- int x,y;
- {
- draw_hex(tmpside,x,y,FALSE);
- }
-